Previous Book Contents Book Index Next

Inside Macintosh: QuickDraw GX Programmer's Overview / Part 2 - The QuickDraw GX Programming Cookbook
Chapter 6 - Handling Graphics


Creating Geometric Shapes

This programming recipe shows you how to use QuickDraw GX to create a small graphics application that allows the user to create path shapes with a series of four mouse clicks, as shown in Figure 6-1.

Figure 6-1 Building a path shape

The user builds a path shape by clicking the mouse in the application's window. Each time the user clicks the mouse, the sample application draws
a small black square representing a geometric point. These small black
squares are called geometry control handles. Later recipes in this chapter show how you can allow the user to edit the path shape using these handles.

Note
There is an important distinction between geometric points, introduced in Chapter 2, and geometry control handles, introduced in this chapter. Geometric points are coordinates stored in shape geometries. They are used to define shapes. Geometry control handles are small square shapes used to represent geometric points to your application's users.<8bat>u
When the user has clicked the mouse four times, the sample application creates a path shape using the four specified points as geometric points. For the purposes of this application, the second and third are interpreted as off-
curve control points.

Once the user completes a path shape, they can start creating a new path shape by clicking the mouse again. At this point, the four geometry control handles from the previous path disappear, a new geometry control handle appears, and the path-creating process starts again.

Overview of Recipe Steps

The steps in this recipe show you how to:

    1. Declare and initialize a document structure
    2. Obtain mouse location
    3. Create user-editable path
    4. Deselect the previous path shape
    5. Create a new path shape
    6. Create and draw a geometry control handle
    7. Add a new geometry point to the path shape
    8. Draw the path shape and add it to the window picture
    9. Respond to update events
    10. Dispose of information in the document structure

You need to follow the instructions in each step of this recipe to implement the path-creating program.

Functions Used in This Recipe

QuickDraw GX functions used in this recipe:
GXNewWindowViewPort"QuickDraw GX and the
Macintosh Environment"
QuickDraw GX Environment and Utilities
GXNewPicture"Picture Shapes"
QuickDraw GX Graphics
GXNewShape"Shape Objects"
QuickDraw GX Objects
GXSetShapeFill"Shape Objects"
QuickDraw GX Objects
GXNewRectangle"Geometric Shapes"
QuickDraw GX Graphics
GXDrawShape"Shape Objects"
QuickDraw GX Objects
GXSetPathParts"Geometric Shapes"
QuickDraw GX Graphics
GXSetPictureParts"Picture Shapes"
QuickDraw GX Graphics
GXDisposeShape"Shape Objects"
QuickDraw GX Objects
GXDisposeViewPort"View-Related Objects"
QuickDraw GX Objects

Standard Macintosh functions used in this recipe:
GetNewWindow"Window Manager"
Macintosh Toolbox Essentials
ShowWindow"Window Manager"
Macintosh Toolbox Essentials
WaitNextEvent"Event Manager"
Macintosh Toolbox Essentials
FrontWindow"Window Manager"
Macintosh Toolbox Essentials
SelectWindow"Window Manager"
Macintosh Toolbox Essentials
DisposeWindow"Window Manager"
Macintosh Toolbox Essentials
NewPtr"Memory Manager"
Memory
GlobalToLocal"Basic QuickDraw"
Imaging With QuickDraw

This recipe gives a brief description of these functions; you can find complete reference information for these functions in the Inside Macintosh suite of books.

This recipe also uses functions from the QuickDraw GX libraries:
SetDefaultViewPorttransform library
SetShapeFastXorTransfertransferMode library

Recipe Step Descriptions

In this section, each step is described individually.

  1. Declare and initialize a document structure

    Macintosh applications frequently store document-related information in a document information structure referenced by the window record that represents the document's window. (Here the term document refers to the group of path shapes drawn in the window.) This recipe uses a structure that contains this information about the document:

    • the number of geometric points the user has specified so far in the path shape being created
    • a reference to the path shape being created
    • an array of references to the small rectangle shapes used to represent the geometric points that the user is creating
    • a reference to a picture containing all the path shapes that the user has created so far

      Here is the data type you can use to define this document information structure:

      #define kNumOfControls 4

      typedef struct {
      WindowPtr window;
      gxViewPort viewPort;

      long pointCount;
      gxShape selectedPath;
      gxShape controls[kNumOfControls];
      gxShape picture;
      } DocumentInfo, *DocumentPtr;

      Note that for the purpose of this recipe, a path is allowed to have only four control points; thus the fourth click indicates the shape is complete.

      You also need to create a global variable that points to the document information structure for the current document:

      DocumentPtr gCurrent;

      In your initialization function, you need to create a window, create a view port and attach it to the window, and initialize the other fields of the document information structure, as with this code example:

      Ptr       document;
      WindowPtr window;
      int count;

      document = NewPtr(sizeof(DocumentInfo));

      if (document != nil) {
      window = (WindowPtr) GetNewCWindow(rDocWindow, storage,
      (WindowPtr) -1);

          ((WindowPeek) window)->refCon = (long) document;
      document->window = window

          document->viewPort = GXNewWindowViewPort(window);
      SetDefaultViewPort(document->viewPort);

          document->pointCount = 0;
      document->selectedPath = nil;
      for (count = 0; count < kNumOfControls; count++)
      document->controls[count] = nil;
      document->picture = GXNewShape(gxPictureType);

      gCurrent = document; /* store in global variable */

          ShowWindow(window);
      }

      Note that, for simplicity, this recipe uses a window with a single view port (and therefore no scrolling).

      After the code creates the window, it performs the following initializations on the document information structure:

    • The point count is initialized to 0, reflecting the fact that the user hasn't clicked the mouse to create any geometric points yet.
    • The selected path reference is initialized to nil, indicating that no path is currently being created.
    • The four references to the control handle shapes are also initialized to nil, indicating that no geometric handles are currently being displayed.
    • The picture shape is created with a geometry containing no shapes.

  2. Obtain mouse location

    When the user presses the mouse, your application receives information about the event from the standard Macintosh function WaitNextEvent. You can determine the type of event by examining the what field of the event record returned by this function. If the event is a mouse-down event, you examine the where field of the event record to determine where the mouse was clicked. If the mouse was clicked in the content part of a window, you use the FrontWindow standard Macintosh function to determine if it was the frontmost window.

    If the mouse was not clicked in the frontmost window, you can use the standard Macintosh function SelectWindow to select the window. At this time, you would probably want to update your gCurrent global variable so that it references the document information for the new front window.

    If the mouse was clicked in the frontmost window, the user is specifying a new geometric point. The MyContentClick sample function shown here handles this situation:

    void MyContentClick(WindowPtr window, EventRecord *event)
    {
    Point eventPoint;
    gxPoint localPoint;

        eventPoint = event->where;
    GlobalToLocal(&eventPoint);

        localPoint.x = ff(eventPoint.h);
    localPoint.y = ff(eventPoint.v);

        MyHandleCreatePath(&localPoint); /* See Step 3. */
    }

    The MyContentClick function translates the mouse location from QuickDraw global coordinates to QuickDraw local coordinates, which are the QuickDraw coordinates of the mouse location in the window. Since the window and view port created in Step 1 do not allow for scrolling, it is easy to translate these coordinates into QuickDraw GX local coordinates--all you have to do is convert the integer coordinates of QuickDraw to fixed-point coordinates for QuickDraw GX.

  3. Create user-editable path

    After translating the coordinates, the MyContentClick function calls the MyHandleCreatePath sample function to respond to the user's mouse click.

    The flow of control for the MyHandleCreatePath function is shown here:

    void MyHandleCreatePath(gxPoint *hitPoint)
    {
    /* Declare local variables -- see Step 7. */

        if (gCurrent->pointCount == 0) {
    /* Deselect previous path -- see Step 4 */
    /* Create new path shape -- see Step 5. */
    }

        /* Create & draw a control handle -- see Step 6. */

        gCurrent->pointCount++;

        /* Add new geometry point to path shape -- see Step 7. */

        if (gCurrent->pointCount == kNumOfControls) {
    /* Draw the path and add to picture -- see Step 8. */
    gCurrent->pointCount = 0;
    }
    }

    This function examines the pointCount field of the document information structure to determine how many geometric points the user has specified for the path shape currently being created. If no points have been specified, this is the first point of a new path, so the function deselects the previously selected path and creates a new path shape to represent the new path the user is creating.

    No matter what the point count, the function then draws a geometry control handle where the use clicked the mouse, increments the point count, and adds the new geometric point to the path shape currently being created.

    Finally, the function examines the point count to determine if this is the last point (the fourth point) in the path shape. If so, the function draws the new path shape and adds it to the document picture.

    The next few steps--Steps 4 through 8--show how to implement the parts of the MyHandleCreatePath function.

  4. Deselect the previous path shape

    The first thing your MyHandleCreatePath function (defined in the previous step) does is determine if the user is starting to create a new path shape. If so, you need to determine if there is a previously created path shape. If so, its geometry control handles are still being displayed, so you need to erase them and dispose of the shapes that represent them:

    if (gCurrent->selectedPath != nil) {
    MyErasePathControlHandles();
    MyDisposePathControlHandles();
    GXDisposeShape(gCurrent->selectedPath);
    }

    The MyDisposePathControlHandles sample function is shown here:

    void MyDisposePathControlHandles()
    {
    int index;

        for (index = 0; index < kNumOfControls; index++)
    if (gCurrent->controls[index] != nil)
    GXDisposeShape(gCurrent->controls[index]);
    }

    This function disposes of each of the shapes referenced by the controls array in the document information structure.

  5. Create a new path shape

    The first time the user presses the mouse button, you need to create a new path shape. (You also need to create a new path shape when the user presses the mouse button and the previously created shape is already finished.)

    This new path shape has no geometric points defined yet, so you need to use the GXNewShape function, thus:

    gCurrent->selectedPath = GXNewShape(gxPathType);

    A reference to the new shape is stored in the selectedPath field of the current document information structure. You can set the shape fill property for this new path using the GXSetShapeFill function:

    GXSetShapeFill(gCurrent->selectedPath, gxFrameFill);

    You need to set the shape fill for the new path because the default shape fill for path shapes is a solid shape fill, which would make the path be drawn as a solid area, rather than a contour, when you draw it in Step 8.

  6. Create and draw a geometry control handle

    Whenever the user clicks the mouse, you need to draw a geometry control handle at the specified location. The MyCreateControlHandle sample function shows how you can do this:

    void MyCreateControlHandle(gxShape *theControl, 
    gxPoint *where) {
    gxRectangle smallSquare;

        smallSquare.left = where->x - ff(2);
    smallSquare.top = where->y - ff(2);
    smallSquare.right = where->x + ff(2);
    smallSquare.bottom = where->y + ff(2);

        *theControl = GXNewRectangle(&smallSquare);
    SetShapeFastXorTransfer(*theControl, &gBlack, &gWhite);
    }

    This function takes two parameters. The second parameter specifies where the geometry control handle should be located. Using this location, the function creates a small square shape to represent the geometry control handle and returns a reference to the new shape in its first parameter.

    The MyCreateControlHandle function sets the transfer mode of the new shape using the SetShapeFastXorTransfer QuickDraw GX library function. This function sets the transfer mode of the shape to exclusive-OR so that when you draw the shape (in black and white in this example) it inverts the pixels it's drawn over. This way, you can erase the shape simply by drawing it a second time.

Note
The transfer mode used by the library function SetShapeFastXorTransfer has certain limitations--
for example, it does not work when a single shape is drawn across multiple monitors. <8bat>u To set this transfer mode, you need to specify the foreground and background colors, which you can do using these global variables:

gxColor gWhite;
gxColor gBlack;

gBlack.space = gxGraySpace;
gBlack.element.gray = 0;
gBlack.profile = nil;

gWhite.space = gxGraySpace;
gWhite.element.gray = 0xFFFF;
gWhite.profile = nil;

In your MyHandleCreatePath function (defined in Step 3), you should call the MyCreateControlHandle function using this line of code:

MyCreateControlHandle(&gCurrent->controls[gCurrent->pointCount]
hitPoint);

This function call specifies the hit point as the location of the new geometry control handle, and also specifies that the shape representing the geometry control handle should be stored in the document information structure.

You can then draw the new geometry control handle:

GXDrawShape(gCurrent->controls[gCurrent->pointCount]);

Since you set the transfer mode of the control handle to be exclusive-OR, it inverts anything it is drawn over. For example, if the user presses the mouse over a previously created path shape, the pixels of the previous path shape show through the black control handle as white.

  1. Add a new geometry point to the path shape

    Now that you've drawn the geometry control handle, you need to add a corresponding geometric point to the path shape being built. You can add geometric points to path shapes using the GXSetPathParts function. To use this function, however, you need to store the new point in a multiple-path structure. This structure allows you to specify whether the point is on curve or off curve. You can define and initialize a multiple-path structure using this definition:

    long initialValues[] = {1,              /* just one contour */
    1, /* just one point */
    0x00000000, /* on curve */
    ff(0), ff(0)}; /* initial position */

    This array contains five values: the first indicates that the path being defined has only one contour, the second indicates that the contour has only one point, the third contains bit flags indicating that the point is an on-curve point, and the fourth and fifth specify the initial coordinates of the point. Before you insert this new point into the path shape, you need to specify the actual coordinates of the point:

    initialValues[3] = hitPoint->x;
    initialValues[4] = hitPoint->y;

    You also need to edit the control bits to specify whether the point being added is an on-curve or an off-curve point:

    if ((gCurrent->pointCount == 2) || (gCurrent->pointCount == 3))
    initialValues[2] = 0x80000000; /* off curve */
    else
    initialValues[2] = 0x00000000; /* on curve */

    Then, to turn the array into a multiple-path structure, you can use these statements:

    gxPaths *insertedPoint;

    insertedPoint = (gxPaths *) initialValues;

    Now you have a structure you can use to insert a single point into your path shape. Before you insert the new point, however, you need to edit the fields of this structure so that they specify the correct position.

    You also need to insert the new point into the path using the GXSetPathParts function:

    GXSetPathParts(gCurrent->selectedPath, /* path to edit */
    0, 0, /* insert at end */
    insertedPoint, /* path to insert */
    gxBreakNeitherEdit); /* connect to contour */

    The first parameter to this function specifies the path shape. The second and third parameters specify where to insert the new point and how many old points to replace. Specifying 0 for both of these parameters indicates that you want to insert the new point at the end of the existing geometry.

    The fourth parameter specifies the information to be added to the geometry--
    in this case, the single new geometry point. The final parameter specifies
    how the point should be added. The constant gxBreakNeitherEdit specifies that it should be appended to the existing contour.

  2. Draw the path shape and add it to the window picture

    In your MyHandleCreatePath function (defined in Step 3), you determine if the mouse click is the specifying a fourth, and final, geometry point for the path. If so, the path shape is done and ready to be added to the window picture. You can use the GXSetPictureParts function to add the new path shape to the window:

    GXSetPictureParts(gCurrent->picture, 
    0, 0, 1, /* insert one shape at end */
    &gCurrent->selectedPath,
    nil, /* no overriding styles */
    nil, /* no overriding inks */
    nil); /* no overriding transforms */

    To draw the completed path shape properly, you need to erase the existing geometry control handles, draw the path, and then draw them again,
    like this:

    MyErasePathControlHandles();
    GXDrawShape(gCurrent-selectedPath);
    MyDrawPathControlHandles();

    This is because the geometry control handles have an exclusive-OR transfer mode. This function does the actual drawing of the geometry control handles:

    void MyDrawPathControlHandles()
    {
    int index;

        for (index = 0; index < kNumOfControls; index++)
    if (gCurrent->controls[index] != nil)
    GXDrawShape(gCurrent->controls[index]);
    }

    Thus, the code to erase them actually draws them:

    void MyErasePathControlHandles()
    {
    MyDrawPathControlHandles(); /* XOR drawing = erasing */
    }
  3. Respond to update events

    When your application receives an update event, you need to redraw the contents of the window. You can do this using the MyDrawWindow sample function:

    void MyDrawWindow()
    {
    GXDrawShape(gCurrent->picture);

        if (gCurrent->selectedPath != nil)
    MyDrawPathControlHandles();
    }

    This simple function redraws the contents of the entire window: the picture associated with the window and, if there is a currently selected path shape, the geometry control handles for that shape. Before you call this function, you need to call the standard Macintosh function BeginUpdate, and after you call this function, you need to call the standard Macintosh function EndUpdate.

  4. Dispose of information in the document structure

    When the user of your application closes a window, you need to dispose of the shapes associated with that window (after you give the user a chance to save them, of course). This code disposes of all the shapes referenced by the current document information structure:

    if (gCurrent->selectedPath != nil)
    GXDisposeShape(gCurrent->selectedPath);

    MyDisposePathControlHandles();

    GXDisposeShape(gCurrent->picture);

    GXDisposeViewPort(gCurrent->viewPort);

    You also need to dispose of your window using standard Macintosh function DisposeWindow.

Related Recipes

The following three recipes show how to add more functionality to the path-
editing program:

The last recipe in this chapter, "Dragging Shapes Using Offscreen Bitmaps," on page 217, shows how to allow the user to drag colored circles around a window. This recipe uses an offscreen bitmap to smooth the redrawing used for dragging feedback.

The recipes in Chapter 4, "Using the QuickDraw GX Environment," show you how to initialize QuickDraw GX and set up the QuickDraw GX debugging facilities. You should read the recipes in that chapter before using any recipes in this chapter.

The recipes in Chapter 5, "Using Macintosh Windows," show you how to create Macintosh windows, attach QuickDraw GX view ports to them, and implement zooming, resizing, and scrolling. You need to be familiar with the information in that chapter before you can display QuickDraw GX graphics in a Macintosh window.

The recipes in Chapter 7, "Handling Typography," show you how to create and manipulate typographic, rather than graphics, shapes.

The recipes in Chapter 8, "Printing," show you how to send your graphics and typographic shapes to a printer.


Previous Book Contents Book Index Next

© Apple Computer, Inc.
8 JUL 1996